/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package provider.impl.genomemap.data;

import com.google.common.collect.ImmutableSet;

import commons.provider.ProviderException;
import commons.provider.impl.BaseProviderImpl;
import commons.util.ArrayUtil;

import genomemap.data.KSA00Data;
import genomemap.data.OrganismData;
import genomemap.data.TAB08Data;
import genomemap.model.KSA00;
import genomemap.model.TAB08;
import genomemap.provider.Providers.OrganismDataProvider;
import genomemap.provider.impl.XMLUtil;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

/**
 * @author Susanta Tewari
 * @since Jul 8, 2011
 */
public class OrganismDataProviderImpl extends BaseProviderImpl<OrganismData>
        implements OrganismDataProvider {

    private File dataDir;
    private Validator validator;
    private InputDataImpl1 impl1;

    @Override
    public void setDataDirectory(final File dataDir) throws ProviderException {


        // check if valid dir
        if (!dataDir.isDirectory()) {
            reportError(5, dataDir);
        }

        if ((this.dataDir == null) ||!this.dataDir.equals(dataDir)) {

            this.dataDir = dataDir;

            final Validator local_validator = new Validator(dataDir);

            local_validator.validate();

            this.validator = local_validator;
        }
    }

    @Override
    public Set<Integer> getAvailableLinkageGroups() throws ProviderException {


        // make sure setDataDirectory() was called successfully
        if (validator == null) {
            reportError(10);
        }

        return validator.getAvailableLinkageGroups();
    }

    @Override
    public void setLinkageGroups(final Set<Integer> linkageGroups) throws ProviderException {


        // check specified linkage groups is the subset of the available linkage groups
        final Set<Integer> available_linkage_groups = validator.getAvailableLinkageGroups();

        if (!available_linkage_groups.containsAll(linkageGroups)) {
            reportError(20, linkageGroups, available_linkage_groups);
        }

        this.impl1 = new InputDataImpl1(linkageGroups, dataDir);
    }

    @Override
    public OrganismData create() throws ProviderException {
        return impl1;
    }

    /**
     *
     */
    private static class GenomeInfo {

        private int chLength;
        private double falsePostiveProb;
        private double falseNegativeProb;

        /**
         * Method description
         *
         *
         * @return chromsome length
         */
        public int getChLength() {
            return chLength;
        }

        /**
         * Method description
         *
         *
         * @param chLength description
         */
        public void setChLength(final int chLength) {
            this.chLength = chLength;
        }

        /**
         * Method description
         *
         *
         * @return description
         */
        public double getFalsePostiveProb() {
            return falsePostiveProb;
        }

        /**
         * Method description
         *
         *
         * @param falsePostiveProb description
         */
        public void setFalsePostiveProb(final double falsePostiveProb) {
            this.falsePostiveProb = falsePostiveProb;
        }

        /**
         * Method description
         *
         *
         * @return description
         */
        public double getFalseNegativeProb() {
            return falseNegativeProb;
        }

        /**
         * Method description
         *
         *
         * @param falseNegativeProb description
         */
        public void setFalseNegativeProb(final double falseNegativeProb) {
            this.falseNegativeProb = falseNegativeProb;
        }
    }

    /**
     * An implementation of OrganismData supported by the parent class. It uses flat files specified
     * by the parent class to create such an instance. It manages a delegate for parsing, implements
     * the cache necessary to support frequent use of this instance and supports the read-only
     * behaviour specified by the interface.
     */
    private class InputDataImpl1 implements OrganismData {

        private Map<Integer, List<String>> genesCache          = new HashMap<>();
        private Map<Integer, Object> genotypeDataCache         = new HashMap<>();
        private Map<Integer, ImmutableSet<String>> clonesCache = new HashMap<>();
        private Map<Integer, Object> hybridizationDataCache    = new HashMap<>();
        private Map<Integer, List<String>> probesCache         = new HashMap<>();


        // applies the missing value algorithm on the extracted genotype scores
        private GenotypeMissingValAlgo missingValAlgo =
            GenotypeMissingValAlgo.getNearestScoreAlgo();


        // probe pruning algorithm
        private ProbesPruningAlgo pruningAlgo            = ProbesPruningAlgo.getDefaultAlgo();
        private Map<Integer, GenomeInfo> genomeInfoCache = new HashMap<>();
        private int cloneLength                          = -1;
        private final Set<Integer> linkageGroups;
        private final File filesDir;

        /**
         * Upon successfully creating an instance clients can be reasonablly confident that further
         * exceptions (runtime) while excuting the interface methods are indicative of bugs
         * (programming errors) than modeled exceptions (invariant or contract violations). <p/>
         * Checks are performed (for the specified linkage groups only) to ensure that <ol> <li>
         * necessary files are present</li> <li> files have the correct format, any validation
         * checks</li> <li> data invariants are preserved</li> </ol>
         *
         * @param linkageGroups linkage groups to be supported by the created instance
         * @param filesDir directory containing the specified files, used by a parser delegate
         * @throws ProviderException If any of the checks mentioned above fails
         */
        public InputDataImpl1(final Set<Integer> linkageGroups, final File filesDir)
                throws ProviderException {


            // check presence of necessary files
            Parser.checkPresenceOfNecessaryFiles(linkageGroups, filesDir);


            // check format of necessary files
            Parser.checkFormatOfNecessaryFiles(linkageGroups, filesDir);


            // check data invariants of necessary files
            Parser.checkDataInvariantsOfNecessaryFiles(linkageGroups, filesDir);


            // init
            this.linkageGroups = Collections.unmodifiableSet(linkageGroups);
            this.filesDir      = filesDir;
        }

        @Override
        public Set<Integer> getLinkageGroups() {
            return linkageGroups;
        }

        @Override
        public List<String> genes(final Integer linkageGroup) {


            // illegal argument checking
            check_argument_linkage_group(linkageGroup);

            if (genesCache.get(linkageGroup) == null) {

                final Parser parser = new Parser(linkageGroup, filesDir);

                genesCache.put(linkageGroup, Collections.unmodifiableList(parser.genes()));
            }

            return genesCache.get(linkageGroup);
        }

        /**
         * Caching is done on the full genotype data, not on data for <code>genes</code>.
         * assignment is done only when param "genes" is null.
         *
         * @param linkageGroup description
         * @param genes description
         * @return newly created array for the specified <code>genes</code> is returned.
         */
        @Override
        public TAB08Data createTAB08Data(final Integer linkageGroup, List<String> genes) {


            // illegal argument checking
            check_argument_linkage_group(linkageGroup);

            if (!genes(linkageGroup).containsAll(genes)) {
                throw new IllegalArgumentException("Some genes in " + genes + " are unavailable");
            }


            // check full data cache
            if (genotypeDataCache.get(linkageGroup) == null) {

                final Parser parser = new Parser(linkageGroup, filesDir);

                genotypeDataCache.put(linkageGroup, missingValAlgo.apply(parser.genotypeData()));
            }


            // full data
            byte[][] genotypeData = (byte[][]) genotypeDataCache.get(linkageGroup);

            if (genes != null) {


                // get indices for argument genes
                final int[] indices = ArrayUtil.indicesOf(genes(linkageGroup), genes);

                if (indices == null) {

                    throw new IllegalArgumentException("Some genes in " + genes
                                                       + " are unavailable");
                }


                // apply the indices
                final byte[][] filteredGenotypeData = new byte[genotypeData.length][indices.length];

                for (int i = 0; i < filteredGenotypeData.length; i++) {

                    for (int j = 0; j < filteredGenotypeData[0].length; j++) {
                        filteredGenotypeData[i][j] = genotypeData[i][indices[j]];
                    }
                }

                genotypeData = filteredGenotypeData;
            } else {
                genes = genes(linkageGroup);
            }

            return new TAB08Data(new TAB08(genes), genotypeData);
        }

        @Override
        public ImmutableSet<String> clones(final Integer linkageGroup) {


            // illegal argument checking
            check_argument_linkage_group(linkageGroup);

            if (clonesCache.get(linkageGroup) == null) {

                final Parser parser = new Parser(linkageGroup, filesDir);

                clonesCache.put(linkageGroup, ImmutableSet.copyOf(parser.clones()));
            }

            return clonesCache.get(linkageGroup);
        }

        @Override
        public List<String> probes(final Integer linkageGroup) {


            // illegal argument checking
            check_argument_linkage_group(linkageGroup);

            if (probesCache.get(linkageGroup) == null) {

                final Parser parser = new Parser(linkageGroup, filesDir);


                // cache only the cleaned probes
                final List<String> fullProbes = parser.probes();
                final Set<Integer> removedIndices =
                    pruningAlgo.removedIndices(parser.hybridizationData(), parser.clones(),
                                               fullProbes);
                final List<String> result     = new ArrayList<>();

                for (int i = 0; i < fullProbes.size(); i++) {

                    if (!removedIndices.contains(i)) {
                        result.add(fullProbes.get(i));
                    }
                }

                probesCache.put(linkageGroup, Collections.unmodifiableList(result));
            }

            return probesCache.get(linkageGroup);
        }

        /**
         * Caching is done on the full hybridization data (after correcting for pruned probes). It
         * is crucial that the puning algorithm used is deterministic, else the data returned may
         * not correctly correspond to the <code>probes</code> in the argument or a
         * <code>RuntimeException</code> may result.
         *
         * assignment is done only when param "probes" is null
         *
         * @param linkageGroup description
         * @param probes description
         * @return newly created array for the specified <code>probes</code>
         */
        @Override
        public KSA00Data createKSA00Data(final Integer linkageGroup, List<String> probes) {


            // illegal argument checking
            check_argument_linkage_group(linkageGroup);


            // check full data cache
            if (hybridizationDataCache.get(linkageGroup) == null) {

                final Parser parser = new Parser(linkageGroup, filesDir);


                // create data after correcting for pruned probes
                final byte[][] fullData = parser.hybridizationData();
                final Set<Integer> removedIndices =
                    pruningAlgo.removedIndices(parser.hybridizationData(), parser.clones(),
                                               parser.probes());
                final byte[][] result =
                    new byte[fullData.length][fullData[0].length - removedIndices.size()];

                for (int i = 0; i < fullData.length; i++) {

                    int counter = 0;

                    for (int j = 0; j < fullData[0].length; j++) {

                        if (!removedIndices.contains(j)) {

                            result[i][counter] = fullData[i][j];

                            counter++;
                        }
                    }
                }

                hybridizationDataCache.put(linkageGroup, result);
            }


            // get full data
            byte[][] hdData = (byte[][]) hybridizationDataCache.get(linkageGroup);

            if (probes != null) {


                // get indices for argument probes
                final int[] indices = ArrayUtil.indicesOf(probes(linkageGroup), probes);

                if (indices == null) {

                    throw new IllegalArgumentException("Some probes in " + probes
                                                       + " are unavailable");
                }


                // return new array for the specified probes
                final byte[][] filteredHDData = new byte[hdData.length][indices.length];

                for (int i = 0; i < filteredHDData.length; i++) {

                    for (int j = 0; j < filteredHDData[0].length; j++) {
                        filteredHDData[i][j] = hdData[i][indices[j]];
                    }
                }

                hdData = filteredHDData;
            } else {
                probes = probes(linkageGroup);
            }


            // get genomeinfo
            if (genomeInfoCache.get(linkageGroup) == null) {

                final Parser parser = new Parser(linkageGroup, filesDir);

                genomeInfoCache.put(linkageGroup, parser.getGenomeInfo());
            }

            final GenomeInfo genomeInfo = genomeInfoCache.get(linkageGroup);
            final KSA00 m = new KSA00(probes, genomeInfo.getChLength(), getCloneLength(),
                                      genomeInfo.getFalseNegativeProb(),
                                      genomeInfo.getFalseNegativeProb());

            return new KSA00Data(m, clones(linkageGroup).asList(), hdData);
        }

        private void check_argument_linkage_group(final Integer linkageGroup) {

            if (!getLinkageGroups().contains(linkageGroup)) {

                throw new IllegalArgumentException("Linkage group " + linkageGroup
                                                   + " is unavailable");
            }
        }

        @Override
        public int getCloneLength() {

            if (cloneLength == -1) {
                cloneLength = Parser.getCloneLength(filesDir);
            }

            return cloneLength;
        }
    }

    /**
     * Parses all the necessary files and provides the parent class with data objects.
     *
     * @author Susanta Tewari
     * @version 1.0 Nov 6, 2010
     */
    private static class Parser {

        private static final String FILE_GENOTYPES  = "genotypes.xml";
        private static final String FILE_ASSIGNMENT = "assignment.xml";
        private static final String FILE_PROBES     = "probe_names.xml";
        private static final String FILE_GENOME     = "genome.xml";
        private static int MISSING_VALUE_CODE       = 9;
        private final int linkageGroup;
        private final File filesDir;

        Parser(final int linkageGroup, final File filesDir) {

            this.linkageGroup = linkageGroup;
            this.filesDir     = filesDir;
        }

        static void checkall(final Set<Integer> linkageGroups, final File filesDir)
                throws ProviderException {

            checkPresenceOfNecessaryFiles(linkageGroups, filesDir);
            checkFormatOfNecessaryFiles(linkageGroups, filesDir);
            checkDataInvariantsOfNecessaryFiles(linkageGroups, filesDir);
        }

        static int getCloneLength(final File filesDir) {


            // read the file probe_names.xml @todo close reader
            final File genomeFile  = new File(filesDir, FILE_GENOME);
            final SAXReader reader = new SAXReader();
            Document document      = null;

            try {
                document = reader.read(genomeFile);
            } catch (DocumentException ex) {
                throw new RuntimeException(ex);
            }


            // obtain the genome for the given linkage group
            final Element root = document.getRootElement();

            return Integer.parseInt(root.elementText("clone-length"));
        }

        static Set<Integer> checkAndReturnLinkageGroups(final File filesDir)
                throws ProviderException {

            final Set<Integer> fromGenotypes = getLinkageGroups(filesDir, FILE_GENOME);
            final Set<Integer> fromProbes    = getLinkageGroups(filesDir, FILE_PROBES);

            if (!fromGenotypes.equals(fromProbes)) {
                throw new ProviderException("mismathcing linkage groups");
            }

            final Set<Integer> fromGenome = getLinkageGroups(filesDir, FILE_GENOME);

            if (!fromGenotypes.equals(fromGenome)) {
                throw new ProviderException("mismathcing linkage groups");
            }

            return fromGenotypes;
        }

        /**
         * Method description
         *
         *
         * @param filesDir description
         * @param fileName description
         *
         * @return description
         */
        private static Set<Integer> getLinkageGroups(final File filesDir, final String fileName) {

            final Set<Integer> result = new HashSet<>();


            // read the file probe_names.xml @todo close reader
            final File genotypesFile = new File(filesDir, fileName);
            final SAXReader reader   = new SAXReader();
            Document document        = null;

            try {
                document = reader.read(genotypesFile);
            } catch (DocumentException ex) {
                throw new RuntimeException(ex);
            }


            // obtain the probe names for the given linkage group
            final Element root = document.getRootElement();

            for (final Object obj : root.elements("ch")) {

                final Element element           = (Element) obj;
                final Integer parsedLinkagGroup =
                    Integer.valueOf(element.attribute("id").getText());

                result.add(parsedLinkagGroup);
            }

            return result;
        }

        /**
         * Checks if the necessary files for the specified linkage groups are present in the given
         * directory.
         *
         * @param filesDir directory containing all the necessary files.
         * @throws ProviderException if any of the necessary files for the specified linkage groups
         * is missing linkage groups for which the necessary files are checked
         */
        static void checkPresenceOfNecessaryFiles(final Iterable<Integer> linkageGroups,
                final File filesDir)
                throws ProviderException {

            final List<String> files = Arrays.asList(filesDir.list());

            if (!files.contains(FILE_ASSIGNMENT)) {
                throw new ProviderException("File " + FILE_ASSIGNMENT + " is missing");
            }

            if (!files.contains(FILE_GENOTYPES)) {
                throw new ProviderException("File " + FILE_GENOTYPES + " is missing");
            }

            if (!files.contains(FILE_PROBES)) {
                throw new ProviderException("File " + FILE_PROBES + " is missing");
            }

            if (!files.contains(FILE_GENOME)) {
                throw new ProviderException("File " + FILE_GENOME + " is missing");
            }

            for (final Integer linkageGroup : linkageGroups) {

                final String file = getHybridizationFile(linkageGroup);

                if (!files.contains(file)) {
                    throw new ProviderException("File " + file + " is missing");
                }
            }
        }

        /**
         * file name: ch_<linkageGroup>.cosmid
         *
         * @param linkageGroup description
         *
         * @return description
         */
        private static String getHybridizationFile(final Integer linkageGroup) {
            return "ch_" + linkageGroup + ".cosmid";
        }

        /**
         * @param linkageGroups linkage groups of the necessary files of which the formats are
         * checked
         * @param filesDir directory containing all the necessary files.
         * @throws ProviderException If any format error occurs
         */
        static void checkFormatOfNecessaryFiles(final Set<Integer> linkageGroups,
                final File filesDir)
                throws ProviderException {

            try {

                XMLUtil.validateSchema(new File(filesDir, FILE_GENOTYPES));
                XMLUtil.validateSchema(new File(filesDir, FILE_PROBES));
                XMLUtil.validateSchema(new File(filesDir, FILE_GENOME));
                XMLUtil.validateSchema(new File(filesDir, FILE_ASSIGNMENT));

            } catch (Exception ex) {
                throw new ProviderException(ex);
            }
        }

        /**
         * @param linkageGroups linkage groups of the necessary files of which the data invariants
         * are checked
         * @param filesDir directory containing all the necessary files.
         * @throws ProviderException If any data invariance fails
         */
        static void checkDataInvariantsOfNecessaryFiles(final Set<Integer> linkageGroups,
                final File filesDir)
                throws ProviderException {}

        static Set<Integer> getAvailabaleLinkageGroups() throws ProviderException {
            return null;
        }

        /**
         * @return genes in genotypes.xml for this linkage group
         * @throws RuntimeException If any file reading or parsing error occurs
         */
        List<String> genes() {

            final List<String> genes = new ArrayList<>();


            // get the genotype text
            final String genotypeText = getGenotypeText();


            // parse the genotype text for genes
            for (String line : genotypeText.split("\n")) {

                line = line.trim();

                if (line.length() > 18) {

                    final char pos18 = line.charAt(17);

                    if ((pos18 == 'M') || (pos18 == 'O') || (pos18 == '-')) {
                        processGenes(line.substring(0, 17), genes);
                    } else {


                        // only genes (line length > 18)
                        processGenes(line, genes);
                    }

                } else {


                    // only genes (line length <= 18)
                    processGenes(line, genes);
                }
            }

            return genes;
        }

        /**
         * Extracts all genotype scores as is from genotypes.xml for this linkage group (may contain
         * missing values coded by {@link #MISSING_VALUE_CODE MISSING_VALUE_CODE}). Rows indicate
         * samples and columns genes.
         *
         * @return all genotype scores with missing values for this linkage group.
         * @throws RuntimeException If any file reading or parsing error occurs
         */
        byte[][] genotypeData() {


            // parse the genotype information in scores strings
            final List<String> scores = new ArrayList<>();


            // get the genotype text
            final String genotypeText = getGenotypeText();


            // parse genotype text for scores
            String genotypeScore = null;

            for (String line : genotypeText.split("\n")) {

                line = line.trim();

                if (line.length() > 18) {

                    final char pos18 = line.charAt(17);

                    if ((pos18 == 'M') || (pos18 == 'O') || (pos18 == '-')) {


                        // genes and scores
                        genotypeScore = line.substring(17).replace("M", "1").replace("O",
                                                       "0").replace("|", " ").replace("-",
                                                           "" + MISSING_VALUE_CODE);

                        processScores(line.substring(0, 17), genotypeScore, scores);
                    } else {


                        // only genes (line length > 18)
                        processScores(line, genotypeScore, scores);
                    }

                } else {


                    // only genes (line length <= 18)
                    processScores(line, genotypeScore, scores);
                }
            }


            // turn the score strings into int array
            final byte[][] data1 = new byte[scores.size()][];

            for (int k = 0; k < scores.size(); k++) {

                final String[] vals = scores.get(k).split("\\s");
                final byte[] row    = new byte[vals.length];

                for (int i = 0; i < vals.length; i++) {
                    row[i] = Byte.valueOf(vals[i]);
                }

                data1[k] = row;
            }


            // transpose the array row: samples col: genes (original had row: gene, col: samples)
            final byte[][] data2 = new byte[data1[0].length][data1.length];

            for (int i = 0; i < data1.length; i++) {

                for (int j = 0; j < data1[0].length; j++) {
                    data2[j][i] = data1[i][j];
                }
            }

            return data2;
        }

        /**
         * @return clones in ch_[LINKAGE-GROUP].cosmid for this linkage group
         * @throws RuntimeException If any file reading or parsing error occurs
         */
        List<String> clones() {

            final List<String> clones = new ArrayList<>();


            // define the cosmid file
            final File cosmidFile = new File(filesDir, getHybridizationFile(linkageGroup));


            // define the reader and parse
            try {

                final BufferedReader reader = new BufferedReader(new FileReader(cosmidFile));

                reader.readLine();    // ignore the first line

                String line = null;

                while ((line = reader.readLine()) != null) {
                    clones.add(line.split("\\s")[0]);
                }

            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }

            return clones;
        }

        /**
         * @return probes in probe_names.xml for this linkage group
         * @throws RuntimeException If any file reading or parsing error occurs
         */
        List<String> probes() {

            final List<String> probesList = new ArrayList<>();


            // get the genotype text
            final String probeNamesText = getProbeNamesText();

            for (final String probe : probeNamesText.split("\\s")) {

                if (probe.length() > 0) {
                    probesList.add(probe);
                }
            }

            return probesList;
        }

        /**
         * parses hybridization data from file {@link Parser#getHybridizationFile(java.lang.Integer)
         * Parser#getHybridizationFile} for all probes
         */
        byte[][] hybridizationData() {

            byte[][] data = null;


            // define the cosmid file
            final File cosmidFile = new File(filesDir, "ch_" + linkageGroup + ".cosmid");


            // define the reader and parse
            BufferedReader reader = null;

            try {

                reader = new BufferedReader(new FileReader(cosmidFile));


                // read the first line to get the number of probes and clones
                final String firstLine = reader.readLine();
                final String[] sizes   = firstLine.split("\\s");
                final int probeCount   = Integer.valueOf(sizes[0]);
                final int cloneCount   = Integer.valueOf(sizes[1]);

                data = new byte[cloneCount][probeCount];

                int lineCounter = 0;
                String line     = null;

                while ((line = reader.readLine()) != null) {

                    final String text = line.split("\\s")[1];

                    for (int j = 0; j < text.length(); j++) {
                        data[lineCounter][j] = Byte.valueOf("" + text.charAt(j));
                    }

                    lineCounter++;
                }

            } catch (IOException ex) {
                throw new RuntimeException(ex);
            } finally {

                try {

                    if (reader != null) {
                        reader.close();
                    }

                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }

            return data;
        }

        /**
         * Method description
         *
         *
         * @param line description
         * @param genes description
         */
        private void processGenes(final String line, final Collection<String> genes) {

            for (String gene : line.split(",")) {

                gene = gene.replace(" ", "");

                if (gene.length() > 0) {
                    genes.add(gene);
                }
            }
        }

        /**
         * Method description
         *
         *
         * @param line description
         * @param genotypeScore description
         * @param scores description
         */
        private void processScores(final String line, final String genotypeScore,
                                   final Collection<String> scores) {

            for (String gene : line.split(",")) {

                gene = gene.replace(" ", "");

                if (gene.length() > 0) {
                    scores.add(genotypeScore);
                }
            }
        }

        /**
         * Parses genotypes.xml to extract the text containing genes and their genotypes for this
         * linkage group
         *
         * @return string containing genes and their genotypes for this linkage group in
         *         genotypes.xml
         */
        private String getGenotypeText() {


            // read the file genotypes.xml
            final File genotypesFile = new File(filesDir, FILE_GENOTYPES);
            final SAXReader reader   = new SAXReader();
            Document document        = null;

            try {
                document = reader.read(genotypesFile);
            } catch (DocumentException ex) {
                throw new RuntimeException(ex);
            }


            // obtain the gene names for the given linkage group
            final Element root  = document.getRootElement();
            String genotypeText = null;

            for (final Object obj : root.elements("ch")) {

                final Element element           = (Element) obj;
                final Integer parsedLinkagGroup =
                    Integer.valueOf(element.attribute("id").getText());

                if (parsedLinkagGroup.equals(Integer.valueOf(linkageGroup))) {

                    genotypeText = element.getText();

                    break;
                }
            }

            return genotypeText;
        }

        /**
         * Method description
         *
         *
         * @return description
         */
        private String getProbeNamesText() {


            // read the file probe_names.xml @todo close reader
            final File genotypesFile = new File(filesDir, FILE_PROBES);
            final SAXReader reader   = new SAXReader();
            Document document        = null;

            try {
                document = reader.read(genotypesFile);
            } catch (DocumentException ex) {
                throw new RuntimeException(ex);
            }


            // obtain the probe names for the given linkage group
            final Element root    = document.getRootElement();
            String probeNamesText = null;

            for (final Object obj : root.elements("ch")) {

                final Element element           = (Element) obj;
                final Integer parsedLinkagGroup =
                    Integer.valueOf(element.attribute("id").getText());

                if (parsedLinkagGroup.equals(Integer.valueOf(linkageGroup))) {

                    probeNamesText = element.getText();

                    break;
                }
            }

            return probeNamesText;
        }

        /**
         * Method description
         *
         *
         * @return description
         */
        private GenomeInfo getGenomeInfo() {


            // read the file probe_names.xml @todo close reader
            final File genomeFile  = new File(filesDir, FILE_GENOME);
            final SAXReader reader = new SAXReader();
            Document document      = null;

            try {
                document = reader.read(genomeFile);
            } catch (DocumentException ex) {
                throw new RuntimeException(ex);
            }


            // obtain the genome for the given linkage group
            final Element root = document.getRootElement();

            for (final Object obj : root.elements("ch")) {

                final Element element           = (Element) obj;
                final Integer parsedLinkagGroup =
                    Integer.valueOf(element.attribute("id").getText());

                if (parsedLinkagGroup.equals(Integer.valueOf(linkageGroup))) {

                    final GenomeInfo genomeInfo = new GenomeInfo();

                    genomeInfo.setChLength(
                        Integer.parseInt(element.elementText("chromosome-length")));
                    genomeInfo.setFalsePostiveProb(
                        Double.parseDouble(element.elementText("false-positive-prob")));
                    genomeInfo.setFalseNegativeProb(
                        Double.parseDouble(element.elementText("false-negative-prob")));

                    return genomeInfo;
                }
            }

            throw new RuntimeException("Linkage group " + linkageGroup + " not found.");
        }
    }

    /**
     *
     */
    private static class Validator {

        private final File filesDir;
        private Set<Integer> linkageGroups;

        /**
         * Constructs ...
         *
         *
         * @param filesDir description
         *
         * @throws ProviderException
         */
        public Validator(final File filesDir) throws ProviderException {

            this.filesDir = filesDir;


            // validate data directory and find the available linkage groups
            validate();
        }

        /**
         * validates the data directory and also finds the available linkage groups
         *
         * @throws ProviderException
         */
        private void validate() throws ProviderException {

            linkageGroups = Parser.checkAndReturnLinkageGroups(filesDir);

            Parser.checkall(linkageGroups, filesDir);
        }

        Set<Integer> getAvailableLinkageGroups() {
            return linkageGroups;
        }
    }
}
